/*
 * Copyright (C) 2015-2019 Alibaba Group Holding Limited
 */
#include <stdint.h>

#include "aos/kernel.h"
#include "aos/list.h"
#include "aos/hal/gpio.h"
#include "ulog/ulog.h"

#include "pin_mux.h"
#include "fsl_common.h"
#include "fsl_gpio.h"

#include "soc_init.h"

#define MIMX_GPIO_GROUP_NUM 5
#define MIMX_GPIO_NUM_PER_GROUP 32

#define MIMX_GPIO_NUM (MIMX_GPIO_GROUP_NUM * MIMX_GPIO_NUM_PER_GROUP)
#define TAG "MIMX_GPIO"

typedef struct
{
    uint8_t init_flag;
    uint8_t config;
    uint8_t irq_enable_flag;
    gpio_irq_handler_t irq_handler;
    void *arg;
} st_mimx_gpio_info;

static st_mimx_gpio_info g_st_mimxgpio_info[MIMX_GPIO_NUM] = {0};
static aos_mutex_t g_stmimxgpio_mutex = {0};

static int32_t mimx_get_gpio_index_by_pin_info(drv_manufacturer_t *pst_pininf, uint32_t *index)
{
    uint32_t group = 0;

    if (pst_pininf->pin >= MIMX_GPIO_NUM_PER_GROUP)
    {
        LOGE(TAG, "Invalid group base 0x%x pin index %d", (uint32_t)(pst_pininf->base), pst_pininf->pin);
        return -1;
    }

    switch ((uint32_t)(pst_pininf->base))
    {
    case GPIO1_BASE:
        group = 0;
        break;
    case GPIO2_BASE:
        group = 1;
        break;
    case GPIO3_BASE:
        group = 2;
        break;
    case GPIO4_BASE:
        group = 3;
        break;
    case GPIO5_BASE:
        group = 4;
        break;
    default:
        LOGE(TAG, "Invalid group base 0x%x ", (uint32_t)(pst_pininf->base));
        return -1;
    }

    *index = (group * MIMX_GPIO_NUM_PER_GROUP) + pst_pininf->pin;

    return 0;
}

static int32_t mimx_get_gpio_irq_by_pin_info(drv_manufacturer_t *pst_pininf, IRQn_Type *irq_type)
{
    uint32_t group = 0;

    if (pst_pininf->pin >= MIMX_GPIO_NUM_PER_GROUP)
    {
        LOGE(TAG, "Invalid group base 0x%x pin index %d", (uint32_t)(pst_pininf->base), pst_pininf->pin);
        return -1;
    }

    switch ((uint32_t)(pst_pininf->base))
    {
    case GPIO1_BASE:
        if (pst_pininf->pin < 16)
        {
            *irq_type = GPIO1_Combined_0_15_IRQn;
        }
        else
        {
            *irq_type = GPIO1_Combined_16_31_IRQn;
        }
        break;
    case GPIO2_BASE:
        if (pst_pininf->pin < 16)
        {
            *irq_type = GPIO2_Combined_0_15_IRQn;
        }
        else
        {
            *irq_type = GPIO2_Combined_16_31_IRQn;
        }
        break;
    case GPIO3_BASE:
        if (pst_pininf->pin < 16)
        {
            *irq_type = GPIO3_Combined_0_15_IRQn;
        }
        else
        {
            *irq_type = GPIO3_Combined_16_31_IRQn;
        }
        break;
    case GPIO4_BASE:
        if (pst_pininf->pin < 16)
        {
            *irq_type = GPIO4_Combined_0_15_IRQn;
        }
        else
        {
            *irq_type = GPIO4_Combined_16_31_IRQn;
        }
        break;
    case GPIO5_BASE:
        if (pst_pininf->pin < 16)
        {
            *irq_type = GPIO5_Combined_0_15_IRQn;
        }
        else
        {
            *irq_type = GPIO5_Combined_16_31_IRQn;
        }
        break;
    default:
        LOGE(TAG, "Invalid group base 0x%x ", (uint32_t)(pst_pininf->base));
        return -1;
    }

    return 0;
}

static int32_t mimx_add_gpio_info(uint32_t pin_index, uint8_t config)
{
    int32_t ret = 0;

    if (aos_mutex_is_valid(&g_stmimxgpio_mutex) == 0)
    {
        ret = aos_mutex_new(&g_stmimxgpio_mutex);
        if (ret)
        {
            LOGE(TAG, "fail to new mutex %s", __func__);
            return -1;
        }
    }

    aos_mutex_lock(&g_stmimxgpio_mutex, AOS_WAIT_FOREVER);
    g_st_mimxgpio_info[pin_index].config = config;
    g_st_mimxgpio_info[pin_index].init_flag = 1;
    aos_mutex_unlock(&g_stmimxgpio_mutex);

    return 0;
}

static int32_t mimx_remove_gpio_info(uint32_t pin_index)
{
    aos_mutex_lock(&g_stmimxgpio_mutex, AOS_WAIT_FOREVER);
    g_st_mimxgpio_info[pin_index].init_flag = 0;
    g_st_mimxgpio_info[pin_index].irq_enable_flag = 0;
    g_st_mimxgpio_info[pin_index].config = 0;
    g_st_mimxgpio_info[pin_index].irq_handler = NULL;
    g_st_mimxgpio_info[pin_index].arg = NULL;
    aos_mutex_unlock(&g_stmimxgpio_mutex);
}

static int32_t mimx_add_gpio_irq_info(uint32_t pin_index, gpio_irq_handler_t irq_handler, void *arg)
{
    int32_t ret = 0;

    if (g_st_mimxgpio_info[pin_index].init_flag == 0 || g_st_mimxgpio_info[pin_index].config != IRQ_MODE || g_st_mimxgpio_info[pin_index].irq_enable_flag == 1)
    {
        LOGE(TAG, "add pin %d irq info fail for invalid gpio state init %d mode %d enable %d", pin_index, g_st_mimxgpio_info[pin_index].init_flag,
             g_st_mimxgpio_info[pin_index].config, g_st_mimxgpio_info[pin_index].irq_enable_flag);
        return -1;
    }

    aos_mutex_lock(&g_stmimxgpio_mutex, AOS_WAIT_FOREVER);
    g_st_mimxgpio_info[pin_index].irq_handler = irq_handler;
    g_st_mimxgpio_info[pin_index].arg = arg;
    g_st_mimxgpio_info[pin_index].irq_enable_flag = 1;
    aos_mutex_unlock(&g_stmimxgpio_mutex);

    return 0;
}

static int32_t mimx_remove_gpio_irq_info(int32_t pin_index)
{
    int32_t ret = 0;
    st_mimx_gpio_info *pst_irqinfo = NULL;

    if (g_st_mimxgpio_info[pin_index].init_flag == 0 || g_st_mimxgpio_info[pin_index].config != IRQ_MODE || g_st_mimxgpio_info[pin_index].irq_enable_flag == 0)
    {
        LOGE(TAG, "invalid remove pin %d irq info input gpio state init %d mode %d enable %d", pin_index, g_st_mimxgpio_info[pin_index].init_flag,
             g_st_mimxgpio_info[pin_index].config, g_st_mimxgpio_info[pin_index].irq_enable_flag);
    }

    aos_mutex_lock(&g_stmimxgpio_mutex, AOS_WAIT_FOREVER);
    pst_irqinfo->irq_enable_flag = 0;
    pst_irqinfo->irq_handler = NULL;
    pst_irqinfo->arg = NULL;
    aos_mutex_unlock(&g_stmimxgpio_mutex);

    return 0;
}

static void gpio_isr_user_call(GPIO_Type *base, const uint8_t start_pin)
{
    drv_manufacturer_t st_drv_info = {0};
    uint32_t index = 0;
    uint32_t i = 0;
    uint32_t irq_mask = 0;

    st_drv_info.base = base;
    irq_mask = GPIO_PortGetInterruptFlags(base);
    for (i = 0; i < MIMX_GPIO_NUM_PER_GROUP; i++) {
        if (((1 << i) & irq_mask) == (1 << i)) {
            st_drv_info.pin = i;
            mimx_get_gpio_index_by_pin_info(&st_drv_info, &index);
            if ((g_st_mimxgpio_info[index].init_flag == 1)
                && (g_st_mimxgpio_info[index].irq_enable_flag == 1)
                && (g_st_mimxgpio_info[index].irq_handler != NULL)) {
                g_st_mimxgpio_info[index].irq_handler(g_st_mimxgpio_info[index].arg);
                GPIO_PortClearInterruptFlags(base, 1 << i);
            }
        }
    }
}

void GPIO1_Combined_0_15_IRQHandler(void)
{
    krhino_intrpt_enter();
    gpio_isr_user_call(GPIO1, 0);
    krhino_intrpt_exit();
}

void GPIO1_Combined_16_31_IRQHandler(void)
{
    krhino_intrpt_enter();
    gpio_isr_user_call(GPIO1, 16);
    krhino_intrpt_exit();
}

void GPIO2_Combined_0_15_IRQHandler(void)
{
    krhino_intrpt_enter();
    gpio_isr_user_call(GPIO2, 0);
    krhino_intrpt_exit();
}

void GPIO2_Combined_16_31_IRQHandler(void)
{
    krhino_intrpt_enter();
    gpio_isr_user_call(GPIO2, 16);
    krhino_intrpt_exit();
}

void GPIO3_Combined_0_15_IRQHandler(void)
{
    krhino_intrpt_enter();
    gpio_isr_user_call(GPIO3, 0);
    krhino_intrpt_exit();
}

void GPIO3_Combined_16_31_IRQHandler(void)
{
    krhino_intrpt_enter();
    gpio_isr_user_call(GPIO3, 16);
    krhino_intrpt_exit();
}

void GPIO4_Combined_0_15_IRQHandler(void)
{
    krhino_intrpt_enter();
    gpio_isr_user_call(GPIO4, 0);
    krhino_intrpt_exit();
}

void GPIO4_Combined_16_31_IRQHandler(void)
{
    krhino_intrpt_enter();
    gpio_isr_user_call(GPIO4, 16);
    krhino_intrpt_exit();
}

void GPIO5_Combined_0_15_IRQHandler(void)
{
    krhino_intrpt_enter();
    gpio_isr_user_call(GPIO5, 0);
    krhino_intrpt_exit();
}

void GPIO5_Combined_16_31_IRQHandler(void)
{
    krhino_intrpt_enter();
    gpio_isr_user_call(GPIO5, 16);
    krhino_intrpt_exit();
}

int32_t mimx_gpio_init(drv_manufacturer_t *drv_info, gpio_config_t config)
{
    int32_t ret = 0;
    uint32_t index = 0;
    gpio_pin_config_t sw_config;

    if (NULL == drv_info)
    {
        LOGE(TAG, " %s Invalid input ", __func__);
        return -1;
    }

    ret = mimx_get_gpio_index_by_pin_info(drv_info, &index);
    if (ret)
    {
        LOGE(TAG, "Invalid port info %s base 0x%x pin %d", __func__, (uint32_t)(drv_info->base), drv_info->pin);
        return -1;
    }

    if (g_st_mimxgpio_info[index].init_flag)
    {
        LOGE(TAG, "%s pin %d have already init", __func__, index);
        return -1;
    }

    switch (config)
    {
    case IRQ_MODE:
        /*no need to enable the isr*/
        sw_config.direction = kGPIO_DigitalInput;
        sw_config.outputLogic = 0;
        sw_config.interruptMode = kGPIO_IntRisingOrFallingEdge;
        break;
    case INPUT_PULL_UP:
    case INPUT_PULL_DOWN:
        sw_config.direction = kGPIO_DigitalInput;
        sw_config.outputLogic = 0;
        break;
    case OUTPUT_PUSH_PULL:
    case OUTPUT_OPEN_DRAIN_NO_PULL:
    case OUTPUT_OPEN_DRAIN_PULL_UP:
        sw_config.direction = kGPIO_DigitalOutput;
        sw_config.outputLogic = 0;
        break;
    default:
        LOGE(TAG, "mimx gpio %d invalid config %d mode", index, config);
        return -1;
        ;
    }

    GPIO_PinInit(drv_info->base, drv_info->pin, &sw_config);
    ret = mimx_add_gpio_info(index, config);
    if (ret)
    {
        LOGE(TAG, "add pin %d info fail %s", index, __func__);
        return -1;
    }

    return 0;
}

int32_t mimx_gpio_output_high(drv_manufacturer_t *drv_info)
{
    int32_t ret = 0;
    uint32_t index = 0;

    if (NULL == drv_info)
    {
        LOGE(TAG, " %s Invalid input ", __func__);
        return -1;
    }

    /*check drv_info is valid*/
    ret = mimx_get_gpio_index_by_pin_info(drv_info, &index);
    if (ret)
    {
        LOGE(TAG, "Invalid port info %s base 0x%x pin %d", __func__, (uint32_t)(drv_info->base), drv_info->pin);
        return -1;
    }

    if (g_st_mimxgpio_info[index].init_flag == 0)
    {
        LOGE(TAG, "%s pin %d haven't init yet", __func__, index);
        return -1;
    }

    /*set gpio value*/
    GPIO_PinWrite(drv_info->base, drv_info->pin, 1U);

    return 0;
}

int32_t mimx_gpio_output_low(drv_manufacturer_t *drv_info)
{
    int32_t ret = 0;
    uint32_t index = 0;

    if (NULL == drv_info)
    {
        LOGE(TAG, " %s Invalid input ", __func__);
        return -1;
    }

    /*check drv_info is valid*/
    ret = mimx_get_gpio_index_by_pin_info(drv_info, &index);
    if (ret)
    {
        LOGE(TAG, "Invalid port info %s base 0x%x pin %d", __func__, (uint32_t)(drv_info->base), drv_info->pin);
        return -1;
    }

    if (g_st_mimxgpio_info[index].init_flag == 0)
    {
        LOGE(TAG, "%s pin %d haven't init yet", __func__, index);
        return -1;
    }

    /*set gpio value*/
    GPIO_PinWrite(drv_info->base, drv_info->pin, 0U);

    return 0;
}

int32_t mimx_gpio_output_toggle(drv_manufacturer_t *drv_info)
{
    int32_t ret = 0;
    uint32_t pin_value = 0;
    uint32_t index = 0;

    if (NULL == drv_info)
    {
        LOGE(TAG, " %s Invalid input ", __func__);
        return -1;
    }

    /*check drv_info is valid*/
    ret = mimx_get_gpio_index_by_pin_info(drv_info, &index);
    if (ret)
    {
        LOGE(TAG, "Invalid port info %s base 0x%x pin %d", __func__, (uint32_t)(drv_info->base), drv_info->pin);
        return -1;
    }

    if (g_st_mimxgpio_info[index].init_flag == 0)
    {
        LOGE(TAG, "%s pin %d haven't init yet", __func__, index);
        return -1;
    }

    pin_value = GPIO_PinRead(drv_info->base, drv_info->pin);
    pin_value ^= 1;
    GPIO_PinWrite(drv_info->base, drv_info->pin, pin_value);

    return 0;
}

int32_t mimx_gpio_input_get(drv_manufacturer_t *drv_info, uint32_t *pin_value)
{
    int32_t ret = 0;
    uint32_t index = 0;

    if (NULL == drv_info || NULL == pin_value)
    {
        LOGE(TAG, " %s Invalid input ", __func__);
        return -1;
    }

    /*check drv_info is valid*/
    ret = mimx_get_gpio_index_by_pin_info(drv_info, &index);
    if (ret)
    {
        LOGE(TAG, "Invalid port info %s base 0x%x pin %d", __func__, (uint32_t)(drv_info->base), drv_info->pin);
        return -1;
    }

    if (g_st_mimxgpio_info[index].init_flag == 0)
    {
        LOGE(TAG, "%s pin %d haven't init yet", __func__, index);
        return -1;
    }

    /*get pin value*/
    *pin_value = GPIO_PinRead(drv_info->base, drv_info->pin);

    return 0;
}

int32_t mimx_gpio_enable_irq(drv_manufacturer_t *drv_info, gpio_irq_trigger_t trigger,
                             gpio_irq_handler_t handler, void *arg)
{
    int32_t ret = 0;
    uint32_t index = 0;
    uint32_t mask = 0;
    gpio_interrupt_mode_t mimx_irq_mode;
    IRQn_Type irq_num;

    if (NULL == drv_info || NULL == handler)
    {
        LOGE(TAG, "Invalid input %s ", __func__);
        return -1;
    }

    /*check drv_info is valid*/
    ret = mimx_get_gpio_index_by_pin_info(drv_info, &index);
    if (ret)
    {
        LOGE(TAG, "Invalid port info %s base 0x%x pin %d", __func__, (uint32_t)(drv_info->base), drv_info->pin);
        return -1;
    }

    if (g_st_mimxgpio_info[index].init_flag == 0)
    {
        LOGE(TAG, "%s pin %d haven't init yet", __func__, index);
        return -1;
    }

    if (g_st_mimxgpio_info[index].config != IRQ_MODE)
    {
        LOGE(TAG, "%s pin %d config isn't irq mode", __func__, g_st_mimxgpio_info[index].config);
        return -1;
    }

    switch (trigger)
    {
    case IRQ_TRIGGER_RISING_EDGE:
        mimx_irq_mode = kGPIO_IntRisingEdge;
        break;
    case IRQ_TRIGGER_FALLING_EDGE:
        mimx_irq_mode = kGPIO_IntFallingEdge;
        break;
    case IRQ_TRIGGER_BOTH_EDGES:
        mimx_irq_mode = kGPIO_IntRisingOrFallingEdge;
        break;
    default:
        LOGE(TAG, "pin %d Invalid irq trigger tyep %d", index, trigger);
        return -1;
    }

    if (g_st_mimxgpio_info[index].irq_enable_flag == 1)
    {
        LOGE(TAG, "%s pin %d irq mode have already enabled", __func__, index);
        return -1;
    }

    /*add irq info*/
    mimx_add_gpio_irq_info(index, handler, arg);

    mimx_get_gpio_irq_by_pin_info(drv_info, &irq_num);
    GPIO_PinSetInterruptConfig(drv_info->base, drv_info->pin, mimx_irq_mode);
    mask = 1 << drv_info->pin;
    GPIO_PortEnableInterrupts(drv_info->base, mask);
    EnableIRQ(irq_num);
    return 0;
}

int32_t mimx_gpio_disable_irq(drv_manufacturer_t *gpio_drv)
{
    int32_t ret = 0;
    uint32_t index = 0;
    uint32_t mask = 0;

    if (NULL == gpio_drv)
    {
        LOGE(TAG, "%s invalid input", __func__);
        return -1;
    }

    /*check drv_info is valid*/
    ret = mimx_get_gpio_index_by_pin_info(gpio_drv, &index);
    if (ret)
    {
        LOGE(TAG, "Invalid port info %s base 0x%x pin %d", __func__, (uint32_t)(gpio_drv->base), gpio_drv->pin);
        return -1;
    }

    if (g_st_mimxgpio_info[index].init_flag == 0)
    {
        LOGE(TAG, "%s pin %d haven't init yet", __func__, index);
        return 0;
    }

    if (g_st_mimxgpio_info[index].config != IRQ_MODE)
    {
        LOGE(TAG, "%s pin %d config isn't irq mode", __func__, g_st_mimxgpio_info[index].config);
        return 0;
    }

    if (g_st_mimxgpio_info[index].irq_enable_flag == 0)
    {
        LOGE(TAG, "%s pin %d irq mode have already disabled", __func__, index);
        return 0;
    }

    mask = 1 << gpio_drv->pin;
    mimx_remove_gpio_irq_info(index);
    GPIO_PortDisableInterrupts(gpio_drv->base, mask);

    return 0;
}

int32_t mimx_gpio_clear_irq(drv_manufacturer_t *gpio_drv)
{
    int32_t ret = 0;
    uint32_t index = 0;
    uint32_t mask = 0;

    if (NULL == gpio_drv)
    {
        LOGE(TAG, "%s invalid input", __func__);
        return -1;
    }

    /*check drv_info is valid*/
    ret = mimx_get_gpio_index_by_pin_info(gpio_drv, &index);
    if (ret)
    {
        LOGE(TAG, "Invalid port info %s base 0x%x pin %d", __func__, (uint32_t)(gpio_drv->base), gpio_drv->pin);
        return -1;
    }

    if (g_st_mimxgpio_info[index].init_flag == 0)
    {
        LOGE(TAG, "%s pin %d haven't init yet", __func__, index);
        return 0;
    }

    if (g_st_mimxgpio_info[index].config != IRQ_MODE)
    {
        LOGE(TAG, "%s pin %d config isn't irq mode", __func__, g_st_mimxgpio_info[index].config);
        return 0;
    }

    mask = 1 << gpio_drv->pin;

    GPIO_PortClearInterruptFlags(gpio_drv->base, mask);

    return 0;
}

int32_t mimx_gpio_finalize(drv_manufacturer_t *gpio_drv)
{
    int32_t ret = 0;
    uint32_t index = 0;

    if (NULL == gpio_drv)
    {
        LOGE(TAG, "%s invalid input", __func__);
        return -1;
    }

    /*check drv_info is valid*/
    ret = mimx_get_gpio_index_by_pin_info(gpio_drv, &index);
    if (ret)
    {
        LOGE(TAG, "Invalid port info %s base 0x%x pin %d", __func__, (uint32_t)(gpio_drv->base), gpio_drv->pin);
        return -1;
    }

    if (g_st_mimxgpio_info[index].init_flag == 0)
    {
        LOGE(TAG, "%s pin %d haven't init yet", __func__, index);
        return 0;
    }

    mimx_remove_gpio_info(index);
    return 0;
}

const st_gpio_opt_t g_stmimx_gpio_opt = {
    .init = mimx_gpio_init,
    .finalize = mimx_gpio_finalize,
    .output_high = mimx_gpio_output_high,
    .output_low = mimx_gpio_output_low,
    .output_toggle = mimx_gpio_output_toggle,
    .input_get = mimx_gpio_input_get,
    .enable_irq = mimx_gpio_enable_irq,
    .disable_irq = mimx_gpio_disable_irq,
    .clear_irq = mimx_gpio_clear_irq,
};
